#include	"controller.h"

int
main(int argc, char **argv)
{
	int			i, index, maxi, maxfd, listenfd, connfd, sockfd, oldfd;
	int			nready, client[MAXCLIENTS];
	ssize_t			n;
	fd_set			rset, allset;
	char			buf[MAXLINE];
	socklen_t		clilen;
	struct sockaddr_in	cliaddr, servaddr;
	struct game		games[MAXGAMES];
		
	for(i = 0; i < MAXGAMES; i++)
		bzero(&games[i], sizeof(games[i]));

	listenfd = socket(AF_INET, SOCK_STREAM, 0);

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family      = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port        = htons(SERV_PORT);

	bind(listenfd, (SA *) &servaddr, sizeof(servaddr));

	listen(listenfd, LISTENQ);

	maxfd = listenfd;			/* initialize */
	maxi = -1;				/* index into client[] array */
	for (i = 0; i < FD_SETSIZE; i++)
		client[i] = -1;			/* -1 indicates available entry */
	FD_ZERO(&allset);
	FD_SET(listenfd, &allset);


	for ( ; ; ) {
		rset = allset;		/* structure assignment */
		nready = select(maxfd+1, &rset, NULL, NULL, NULL);

		if (FD_ISSET(listenfd, &rset)) {	/* new client connection */
			clilen = sizeof(cliaddr);
			connfd = accept(listenfd, (SA *) &cliaddr, &clilen);
#ifdef	NOTDEF
			printf("new client: %s, port %d\n",
					Inet_ntop(AF_INET, &cliaddr.sin_addr, 4, NULL),
					ntohs(cliaddr.sin_port));
#endif

			for (i = 0; i < FD_SETSIZE; i++)
				if (client[i] < 0) {
					client[i] = connfd;	/* save descriptor */
					break;
				}
			if (i == FD_SETSIZE){
				perror("too many clients");
				exit(1);
			}


                        
			FD_SET(connfd, &allset);	/* add new descriptor to set */
			if (connfd > maxfd)
				maxfd = connfd;			/* for select */
			if (i > maxi)
				maxi = i;				/* max index in client[] array */

			if (--nready <= 0)
				continue;				/* no more readable descriptors */
		}

		for (i = 0; i <= maxi; i++) {	/* check all clients for data */
			if ( (sockfd = client[i]) < 0)
				continue;

			if (FD_ISSET(sockfd, &rset)) {
				if ( (n = readMsg(sockfd, buf)) <= 0) {
						/*4connection closed by client */
					close(sockfd);
					FD_CLR(sockfd, &allset);
					client[i] = -1;
				} else{
					if(n = inspectMsg(sockfd, buf, games) < 0){
						perror("inspectMsg");
						exit(1);
					}
					else if(n > 0){
/*						closeTCP(n, allset, client);
this is supposed to be hidden in a function..*/
						oldfd = n;
						close(oldfd);
						FD_CLR(oldfd, &allset);
						if((index = getIndex(oldfd, client)) < 0){
							perror("getIndex");
							return -1;
						}
						client[index] = -1;
					}
				}

				if (--nready <= 0)
					break;		/* no more readable descriptors */
			}
		}
	}
}


int inspectMsg(int sockfd, char *msg, struct game *games)
{
/*this function is used to inspect the message that was received and act according to the message contents*/

	int gameid;
	int n;
	int sendfd;
	int playercount;
	int playernum;
	gameid = getGameId(msg);
	sendfd = getSendfd(gameid, games);
	playercount = games[gameid].player_count;
	playernum = msg[2];

	switch(msg[0]){

		case GAME_HOST_READY: 	if(n = doNewGameHost(sockfd, msg, games) < 0)
						return -1; 
			  		else if(n == 1){
						return 2;/*to indicate we have to close the former first TCP*/
			  		}
					break;

		case GAME_HOST_JOIN_OK: /*NOP*/
				break;

		case PLAYER_READY: doAddNewPlayer(gameid, sendfd, playercount);
				break;

		case TURN: doSendTurn(gameid, sendfd);
				break;

		case SHOOT: doSendShoot(gameid, sendfd, playernum);
				break;

		case SHOOT_RESPONSE: /*NOP*/
				break;

		case PLAYER_QUIT: doPlayerQuit(gameid, sendfd, games);
				break;

		case GAME_END: doGameEnd(gameid, sendfd);
				break;

		default:  return -1;
	}
	return 0;
}

int doNewGameHost(int sockfd, char *msg, struct game *games){

	int                 gameid, makecon;
	struct sockaddr_in  cliaddr;
	socklen_t           clilen = sizeof(cliaddr);
	int                 playernum;
	
	char                specs[MSGSIZE - 3];
        char                **specsptr;
	int                 i, j;
	int                 oldfd;
        int                 msglen;

	oldfd = games[gameid].sendfd;

	if((gameid = getFreeSlot(games)) < 0){
		perror("getFreeSlot");	
		return -1;
	}

	if(getpeername(oldfd, (SA *) &cliaddr, &clilen) < 0){
		perror("getpeername");
		return -1;
	      /*errquit(1); not yet defined..*/
	}

	games[gameid].game_ID = gameid;
	games[gameid].host_count++;
	games[gameid].player_count++;
	games[gameid].sendfd = sockfd;
	games[gameid].firstSendAddr = cliaddr;
	games[gameid].clilen = clilen;
	playernum = games[gameid].player_count;

	if(allocateShips(SIZE_X, SIZE_Y) < 0){ 
		return -1;
	}

	for(i = 0; i < (MAXSHIPS * SHIPLENGTH * 2); i++)
		games[gameid].ships[i] = ships[i];
	
	makecon = 0;
	if(games[gameid].host_count > 1)
		makecon = 1;

	for(j = 0; j < sizeof(specs); j++)
		specs[j] = 0;
  
	getSpecs(&specs, (SA *) &cliaddr, makecon);

        specsptr = (char**) &specs;
	sendMsg(GAME_HOST_JOIN_OK, gameid, playernum, sockfd, specsptr);
         
	if(games[gameid].host_count > 2)
		return 1; /*to indicate that the former TCP connection can be closed*/ 

	return 0;
}

int getFreeSlot(struct game *games)
{	/*returns the first game that has space for another player*/

	int j;
	for(j = 0; j < MAXGAMES; j++){
		if(games[j].host_count < MAXHOSTS)
			return games[j].game_ID;
	}
	return -1;
}

int allocateShips(int size_x, int size_y){
	/*studipdly done so far...*/
	int i;

	for(i = 0; i < (MAXSHIPS * SHIPLENGTH * 2); i += 6){
		ships[i] = i;
		ships[i + 1] = i;
		ships[i + 2] = i + 1;
		ships[i + 3] = i;
		ships[i + 4] = i + 2;
		ships[i + 5] = i;
	}
	return 0; 
}


int sendMsg(int msgid, int gameid, int sender, int sendfd, char **specs)
{
	char msg[MSGSIZE];
	int i;
	msg[0] = msgid;
	msg[1] = gameid;
	msg[2] = sender; /*in certain messages(GAME_HOST_JOIN_OK), this field is used to send other information*/

	for(i = 3; i < MSGSIZE; i++){
		msg[i] = *specs[i - 3];
	}
	if(writeMsg(sendfd, msg) < 0)
		return -1;
}

/*makes one table of all the message specific information. we have to serialize the data to be able to send the information in the struct in correct format. this means we send all the data in text format. all the information is put in a char* buffer to do this. first we send the size of the clientaddr struct so that the receiver can then deserialize it. */

int getSpecs(char **specs, struct sockaddr *cliaddr, int makecon)
{
        int ptr = 0;
        int addrlen = 0;
        
        if(inet_ntop(AF_INET,  &cliaddr, *specs + 1, MSGSIZE-3) == 0){
            perror("inet_ntop");
            return -1;
        }
        //TODO pitää parsettaa..
        ptr = strlen(*specs+1);
        addrlen = ptr;
        *specs[0] = addrlen;
        ptr++;
	memcpy(*specs + ptr, ships, SHIPSIZE);
	ptr += SHIPSIZE;
	*specs[ptr] = makecon;

	return 1;
}

int getGameId(char *msg){

	return msg[0];
}

int getSendfd(int gameid, struct game *games){

	int sendfd;
	sendfd = games[gameid].sendfd; /*should check for errors...*/
	return sendfd;
}

int doAddNewPlayer(int gameid, int sendfd, int playercount){

	/*if player count is either 1 or > 2, nothing is done. Either we have to wait for another player or the game is already running and nothing needs to be done. playercount can never be 0 because it is increased by 1 before this function is called.*/

	if(playercount == 1)
		return -1; 

	else if(playercount == 2){
		doSendTurn(gameid, sendfd);
		return 0;
	}
	else return 1;	
}

/*sends the turn message to the first game address*/

int doSendTurn(int gameid, int sendfd){
	
	if(sendMsg(TURN, gameid, 0, sendfd, NULL) < 0){ /*sender is set to 0 to indicate that we are not interested in it's value in TURN messages*/

		perror("sendMsg");
		exit(1);
	}
	return 0;
}

int doSendShoot(int gameid, int sendfd, int sender){

	if(sendMsg(SHOOT, gameid, sender, sendfd, NULL) < 0){
		perror("sendMsg");
		exit(1);
	}
	return 0;
}

int doPlayerQuit(int gameid, int sendfd, struct game *games){
	
	games[gameid].player_count--;

	if(games[gameid].player_count < 2){
		if((sendMsg(GAME_END, gameid, CONTROLLER, sendfd, NULL)) < 0)
			return -1;
	}
	return 0;
}

void doGameEnd(int gameid, int sendfd){

	sendMsg(GAME_END, gameid, CONTROLLER, sendfd, NULL);
	exit(0);
}

int readMsg(int sockfd, char *msg)
{
/*this function is used to read the standard sized message, which size is defined by macro MSGSIZE. The message that is read is read to location pointed by msg*/

	size_t		nleft, n;
	ssize_t		nread;
	char 		*ptr;
	
	ptr = msg;
	n = MSGSIZE;
	nleft = n;

	while(nleft > 0){
		if(nread = read(sockfd, ptr, nleft) < 0){
			if(errno == EINTR)
				nread = 0;
			else 
				return -1;
		}
		else if(nread == 0)
			break;
		nleft -= nread;
		ptr += nread;
	}
	return (n - nleft);
}

int writeMsg(int sockfd, const char *msg)
{
/*this function is used to send the standard sized message to the socket defined by sockfd. Message size is defined by the macro MSGSIZE.*/

	size_t 		nleft, n;
	ssize_t		nwritten;
	const char	*ptr;
	
	ptr = msg;
	n = MSGSIZE;
	nleft = MSGSIZE;

	while(nleft > 0){
		if((nwritten = write(sockfd, ptr, nleft)) <= 0){
			if(errno == EINTR)
				nwritten = 0;
			else 
				return -1;
		}
		nleft -= nwritten;
		ptr += nwritten;
	}
	return n;
}

int getIndex(int fd, int *client){
/*this function fetches the index of the filedescriptor from the table client*/
	
	int i;
	for(i = 0; i < MAXCLIENTS; i++){
		if(client[i] == fd)
			return i;
	}
	return -1;
}
